﻿using System;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Imaging;
using Acid.OpenGLES;

namespace Acid.Graphics
{
    /// <summary>
    /// A 2 Dimentional Texture.
    /// </summary>
    unsafe public class Texture2D : ITexture
    {
        private Device      device;
        private uint        handle;
        private int         width;
        private int         height;


        private Texture2D() { }

        #region Factory Methods


        private static void RGBtoBGR(BitmapData bitmapData, int width, int height)
        {
            int count = 0;

            byte* ptr = (byte *)bitmapData.Scan0.ToPointer();

            for (int i = 0; i < (width * height); i++)
            {
                byte temp = ptr[count + 0];

                ptr[count + 0] = ptr[count + 2];

                ptr[count + 2] = temp;

                count += 3;
            }
        }
        /// <summary>
        /// TOO SLOW
        /// </summary>
        /// <param name="bitmap"></param>
        public static void FlipY(Bitmap bitmap)
        {
            Color[,] colors = new Color[bitmap.Width, bitmap.Height];

            for (int i = 0; i < bitmap.Width; i++)
            {
                for (int j = 0; j < bitmap.Height; j++)
                {
                    colors[i, j] = bitmap.GetPixel(i, j);
                }
            }

            for (int i = 0; i < bitmap.Width; i++)
            {
                for (int j = bitmap.Height - 1; j > -1; j--)
                {
                    bitmap.SetPixel(i, j, colors[i, j]);
                }
            }
        }

        /// <summary>
        /// Loads a Texture2D from a System.Drawing.Bitmap object.
        /// </summary>
        /// <param name="device"></param>
        /// <param name="bitmap"></param>
        /// <returns></returns>
        public static Texture2D FromBitmap(Device device, Bitmap bitmap)
        {
            Texture2D texture  = new Texture2D();
            texture.device     = device;
            texture.width      = bitmap.Width;
            texture.height     = bitmap.Height;
        
            
            // internal static extern int GdipImageRotateFlip(HandleRef image, int rotateFlipType);
            // [DllImport("gdiplus.dll", CharSet=CharSet.Unicode, SetLastError=true, ExactSpelling=true)]
            

            //bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
            BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0,
                                                                  bitmap.Width,
                                                                  bitmap.Height),
                                                                  ImageLockMode.ReadOnly,
                                                                  PixelFormat.Format24bppRgb);
            RGBtoBGR(bitmapData, bitmap.Width, bitmapData.Height);

            

            


            GCHandle gch = GCHandle.Alloc(texture.handle, GCHandleType.Pinned);

            gl.GenTextures(1, (uint*)gch.AddrOfPinnedObject());

            gch.Free();

            gl.BindTexture(gl.GL_TEXTURE_2D, texture.handle);
            gl.TexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGB,
                                                  (int)texture.Width,
                                                  (int)texture.Height, 0,
                                                  gl.GL_RGB,
                                                  gl.GL_UNSIGNED_BYTE,
                                                  bitmapData.Scan0);

            gl.TexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST);
            gl.TexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST);
            gl.BindTexture(gl.GL_TEXTURE_2D, 0);

            bitmap.UnlockBits(bitmapData);

            return texture;
        }
        /// <summary>
        /// <para>Loads a Texture from File.</para>
        /// <para>This will load any image that a System.Drawing.Bitmap will load.</para>
        /// </summary>
        /// <param name="device"></param>
        /// <param name="filename"></param>
        /// <returns></returns>
        public static Texture2D FromFile(Device device, string filename)
        {
            Bitmap bitmap = new Bitmap(filename);

            Texture2D texture2D = Texture2D.FromBitmap(device, bitmap);

            return texture2D;
        }
        /// <summary>
        /// Creates an empty texture.
        /// </summary>
        /// <param name="device"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="format"></param>
        /// <returns></returns>
        public static Texture2D Empty(Device device, int width, int height)
        {
            return Texture2D.Empty(device, width, height, 0);
        }
        /// <summary>
        /// Creates an empty texture.
        /// </summary>
        /// <param name="device"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="format"></param>
        /// <returns></returns>
        public static Texture2D Empty(Device device, int width, int height, int border)
        {
            Texture2D texture = new Texture2D();
            texture.device = device;
            texture.width  = width;
            texture.height = height;

            GCHandle gch = GCHandle.Alloc(texture.handle, GCHandleType.Pinned);

            gl.GenTextures(1, (uint*)gch.AddrOfPinnedObject());

            gch.Free();

            gl.BindTexture(gl.GL_TEXTURE_2D, texture.handle);
            gl.TexImage2D(gl.GL_TEXTURE_2D, 0,   gl.GL_RGB,
                                                 (int)texture.width,
                                                 (int)texture.height, border,
                                                 gl.GL_UNSIGNED_BYTE,
                                                 gl.GL_RGB,
                                                 IntPtr.Zero);
            gl.TexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST);
            gl.TexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST);
            gl.BindTexture(gl.GL_TEXTURE_2D, 0);

            return texture;
        }
        #endregion

        #region Properties

        public Device Device
        {
            get { return this.device; }
        }
        
        public int Width
        {
            get { return this.width; }
        }
        public int Height
        {
            get { return this.height; }
        }
        public uint Handle
        {
            get { return this.handle; }
            set { this.handle = value; }
        }

        #endregion

        #region ITexture Members
        public TextureFilter MinFilter
        {
            set
            {
                gl.BindTexture(gl.GL_TEXTURE_2D, this.handle);
                gl.TexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, (int)value);
                gl.BindTexture(gl.GL_TEXTURE_2D, 0);
            }
        }
        public TextureFilter MagFilter
        {
            set
            {
                gl.BindTexture(gl.GL_TEXTURE_2D, this.handle);
                gl.TexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, (int)value);
                gl.BindTexture(gl.GL_TEXTURE_2D, 0);
            }
        }
        public void SetData<T>(T[] data)
        {
            GCHandle gch = GCHandle.Alloc(data, GCHandleType.Pinned);

            gl.BindTexture(gl.GL_TEXTURE_2D, this.handle);

            gl.TexImage2D(gl.GL_TEXTURE_2D, 0,
                            gl.GL_RGB,
                            (int)this.width,
                            (int)this.height,
                            0,
                            gl.GL_UNSIGNED_BYTE,
                            gl.GL_RGB,
                            gch.AddrOfPinnedObject());

            gl.BindTexture(gl.GL_TEXTURE_2D, 0);

            gch.Free();
        }
        
        #endregion

        #region IDisposable Members


        private bool disposed = false;

        private void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    GCHandle gch = GCHandle.Alloc(this.handle, GCHandleType.Pinned);

                    gl.DeleteTextures(1, (uint *)gch.AddrOfPinnedObject());

                    gch.Free();


                }

                this.disposed = true;
            }
        }

        public void Dispose()
        {
            this.Dispose(true);

            GC.SuppressFinalize(this);
        }

        ~Texture2D()
        {
            this.Dispose(false);
        }


        #endregion

        
    }
}
